﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.IO;
using System.Xml;

namespace AlgorithmBox.FlowCharts
{
	/// <summary>
	/// Interaction logic for FlowChart.xaml
	/// </summary>
	public partial class FlowChart : UserControl
	{
		public bool IsDrawing = false;
		public object DrawingElement = null;
		public FlowChartElement SelectedElement = null;

		public FlowChart()
		{
			InitializeComponent();
		}

		public void StartAnimation()
		{
			DashStyle st = new DashStyle();
			st.Dashes = linePen.DashStyle.Dashes;
			st.Offset = linePen.DashStyle.Offset;
			linePen.DashStyle = st;

			//create animation
			DoubleAnimation anim = new DoubleAnimation(-21, TimeSpan.FromSeconds(5));
			anim.RepeatBehavior = RepeatBehavior.Forever;
			linePen.DashStyle.BeginAnimation(DashStyle.OffsetProperty, anim);
		}

		public void Deselect()
		{
			this.SelectedElement.IsSelected = false;
			this.SelectedElement = null;
		}

		#region FlowChart Mouse Events

		//TODO: move this method to flowchartelement
		Point m; //TODO: Give a valid name
		private void FlowChart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			Point clickPoint = e.GetPosition(this);
			//Hit Test
			DependencyObject element = VisualTreeHelper.HitTest(this, clickPoint).VisualHit;

			string result = String.Empty;
			while ((element != null) && !(element is FlowChartElement))
			{
				element = VisualTreeHelper.GetParent(element);
			}

			if ((element is FlowChartElement))
			{
				if (SelectedElement != null)
					SelectedElement.IsSelected = false;
				SelectedElement = (FlowChartElement)element;
				SelectedElement.IsSelected = true;
				int maxTabIndex = GetTopMostItemIndex();
				if ((int)SelectedElement.GetValue(Panel.ZIndexProperty) < maxTabIndex)
					SelectedElement.SetValue(Panel.ZIndexProperty, maxTabIndex + 1);
				m = e.GetPosition(SelectedElement);
			}
			else
			{
				if (SelectedElement != null)
					((ISelectable)SelectedElement).IsSelected = false;
				SelectedElement = null;
				m = new Point(0.0, 0.0);
			}

			//TODO: refactor to a method named Add element
			if (IsDrawing && (DrawingElement != null))
			{

				//Connection----------------------------------------------------
				if ((this.DrawingElement is ConnectionLine) && (SelectedElement != null))
				{
					ConnectionLine conElement = (ConnectionLine)DrawingElement;

					conElement.NextState();
					if (conElement.IsFinish) //2nd
					{
						//new connection EndPoint
						conElement.Line.EndPoint = new Point(
							(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
							(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
						//two-way binding
						conElement.Next = SelectedElement;

						SelectedElement.PrevLink.Add(conElement);
						this.connectionLinesGeometry.Children.Add(conElement.Line);
						//release element
						DrawingElement = null;
					}
					else //1st
					{

						//remove older connection
						if (SelectedElement.Link != null)
							this.connectionLinesGeometry.Children.Remove(SelectedElement.Link.Line);

						//new connection StartPoint
						conElement.Line.StartPoint = new Point(
							(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
							(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);


						if (SelectedElement is Decision)
						{
							Decision decision = (Decision)SelectedElement;
							if (decision.GetLinkStatus())
							{
								if (decision.TrueLink != null)
									this.connectionLinesGeometry.Children.Remove(decision.TrueLink.Line);
								decision.TrueLink = conElement;
							}
							else
							{
								if (decision.FalseLink != null)
									this.connectionLinesGeometry.Children.Remove(decision.FalseLink.Line);
								decision.FalseLink = conElement;
							}
						}
						else
						{
							SelectedElement.Link = conElement;
						}
					}

				}
				//DrawingElement-----------------------------------------------
				else if (this.DrawingElement is FlowChartElement)
				{
					FlowChartElement flElement = (FlowChartElement)DrawingElement;
					this.Sketch.Children.Add(flElement);
					flElement.SetValue(Canvas.LeftProperty, clickPoint.X - flElement.ActualWidth / 2);
					flElement.SetValue(Canvas.TopProperty, clickPoint.Y - flElement.ActualHeight / 2);
					flElement.SetValue(Panel.ZIndexProperty, GetTopMostItemIndex() + 1);
					DrawingElement = null;
				}
			}
		}

		private int GetTopMostItemIndex()
		{
			int max = Int32.MinValue;
			int current;
			foreach (Visual v in this.Sketch.Children)
			{
				current = (int)v.GetValue(Panel.ZIndexProperty);
				if (current > max)
					max = current;
			}
			return max;
		}

		private void FlowChart_MouseMove(object sender, MouseEventArgs e)
		{
			if ((e.LeftButton == MouseButtonState.Pressed) && (SelectedElement != null))
			{
				Point p = e.GetPosition(Sketch);

				SelectedElement.SetValue(Canvas.LeftProperty, p.X - m.X);
				SelectedElement.SetValue(Canvas.TopProperty, p.Y - m.Y);

				if (SelectedElement.Link != null)
				{
					SelectedElement.Link.Line.StartPoint = new Point(
								(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
								(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
				}
				if (SelectedElement.PrevLink != null)
				{
					foreach (ConnectionLine con in SelectedElement.PrevLink)
						con.Line.EndPoint = new Point(
								(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
								(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
				}
				if (SelectedElement is Decision)
				{
					Decision decision = (Decision)SelectedElement;
					if (decision.TrueLink != null)
					{
						decision.TrueLink.Line.StartPoint = new Point(
									(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
									(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
					}
					if (decision.TruePrevLink != null)
					{
						decision.TruePrevLink.Line.EndPoint = new Point(
									(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
									(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
					}
					if (decision.FalseLink != null)
					{
						decision.FalseLink.Line.StartPoint = new Point(
									(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
									(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
					}
					if (decision.FalsePrevLink != null)
					{
						decision.FalsePrevLink.Line.EndPoint = new Point(
									(double)SelectedElement.GetValue(Canvas.LeftProperty) + SelectedElement.ActualWidth / 2,
									(double)SelectedElement.GetValue(Canvas.TopProperty) + SelectedElement.ActualHeight / 2);
					}
				}
				this.SetCanvasSize();
			}
		}
		private void FlowChart_MouseEnter(object sender, MouseEventArgs e)
		{
			if (IsDrawing)
				this.Cursor = Cursors.Pen;
		}

		private void FlowChart_MouseLeave(object sender, MouseEventArgs e)
		{
			this.Cursor = Cursors.Arrow;
		}

		private void FlowChart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (this.DrawingElement == null)
			{
				IsDrawing = false;
				this.Cursor = Cursors.Arrow;
			}
		}

		#endregion

		public void CheckSelectedElement()
		{
			if (this.SelectedElement != null)
			{
				this.Deselect();
			}
		}

		#region Resize

		public void SetCanvasSize()
		{
			//Get Right Down corner of the Skecth
			double bottom = 0.0;
			double right = 0.0;
			foreach (UIElement ui in this.Sketch.Children)
			{
				double b = (double)ui.GetValue(Canvas.TopProperty)+(double)ui.GetValue(FrameworkElement.ActualHeightProperty);
				double r = (double)ui.GetValue(Canvas.LeftProperty)+(double)ui.GetValue(FrameworkElement.ActualWidthProperty);
				if (bottom < b) bottom = b;
				if (right < r) right = r;
			}

			//Resize sketch depending on elements or parent
			FrameworkElement s = (FrameworkElement)this.Sketch.Parent;

			if (this.Sketch.ActualWidth < right)
				this.Sketch.Width = right;
			else if (right < s.ActualWidth - SystemParameters.VerticalScrollBarWidth)
				this.Sketch.Width = s.ActualWidth - SystemParameters.VerticalScrollBarWidth;

			if (this.Sketch.ActualHeight < bottom)
				this.Sketch.Height = bottom;
			else if (bottom < s.ActualHeight - SystemParameters.HorizontalScrollBarHeight)
				this.Sketch.Height = s.ActualHeight - SystemParameters.HorizontalScrollBarHeight;

			this.drawingBorder.Rect = new Rect(0, 0, this.Sketch.ActualWidth, this.Sketch.ActualHeight);
		}

		#endregion

		private void FlowChart_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.Key)
			{
				case Key.Delete: RemoveSelectedElement(); break;
			}
		}

		private void RemoveSelectedElement()
		{
			if (this.SelectedElement != null)
				this.Sketch.Children.Remove(this.SelectedElement);
			if (this.SelectedElement.Link != null)
				this.connectionLinesGeometry.Children.Remove(this.SelectedElement.Link.Line);

			if (this.SelectedElement is Decision)
			{
				Decision decision = (Decision)this.SelectedElement;
				if (decision.TrueLink != null)
					this.connectionLinesGeometry.Children.Remove(decision.TrueLink.Line);
				if (decision.FalseLink != null)
					this.connectionLinesGeometry.Children.Remove(decision.FalseLink.Line);
			}
			foreach (ConnectionLine con in this.SelectedElement.PrevLink)
				this.connectionLinesGeometry.Children.Remove(con.Line);
		}

		public void SaveToFile(string fileName, List<Variable> variables)
		{
			StringBuilder saveStr = new StringBuilder();

			foreach (Variable v in variables)
				saveStr.AppendLine(String.Format("${0}${1}",
					v.Name,
					v.Type));

			foreach (FlowChartElement fl in this.Sketch.Children.OfType<FlowChartElement>())
			{
				//XmlElement type = doc.CreateElement("Type");
				//type.InnerText = fl.GetType().ToString();

				//doc.AppendChild(type);

				saveStr.Append(String.Join("#",
					fl.GetType(),						//0
					fl.ID,								//1
					fl.GetValue(Canvas.LeftProperty),	//2
					fl.GetValue(Canvas.TopProperty),	//3
					fl.ActualWidth,						//4
					fl.ActualHeight));					//5

				if (fl.Link != null)
				{
					saveStr.Append("#");
					saveStr.Append(String.Join("#",
						fl.Link.Next.ID,				//6
						fl.Link.Line.StartPoint,		//7
						fl.Link.Line.EndPoint));		//8
				}
				//else
				//    saveStr.Append("#0");

				saveStr.AppendLine("#"+ fl.Text);


			}
			File.WriteAllText(fileName, saveStr.ToString());
			//doc.Save(fileName+".xml");
		}

		public void LoadFromFile(string fileName)
		{
			ClearAllElements();
			MainWindow win = (MainWindow)Application.Current.MainWindow;
			win.listVariables.Items.Clear();
			char[] separator = new char[] { '$' };

			foreach (string line in File.ReadAllLines(fileName))
			{
				if (line.StartsWith("$"))
				{
					
					string[] data = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
					//MessageBox.Show(data[0]+" "+data[1]);
					win.listVariables.Items.Add(new Variable() { Name = data[0], Type = data[1] } );
				}
				else
				{
					string[] data = line.Split('#');
					FlowChartElement fl = GetElementByType(data[0]);

					FillDataToElement(fl, data);
					fl.Text = data[data.Length-1];
					fl.ContentChanger.Text = fl.Text;
					this.Sketch.Children.Add(fl);
				}
			}

			foreach (string line in File.ReadAllLines(fileName))
			{
				string[] data = line.Split('#');

				if (data.Length <= 7) continue;

				Point sp = new Point(
					Double.Parse(data[7].Split(',')[0]),
					Double.Parse(data[7].Split(',')[1]));
				Point ep = new Point(
					Double.Parse(data[8].Split(',')[0]),
					Double.Parse(data[8].Split(',')[1]));
				ConnectElements(UInt32.Parse(data[1]), UInt32.Parse(data[6]), sp, ep);
			}
		}

		//DELETE: moved to another place
		FlowChartElement GetElementByType(string typeString)
		{
			switch (typeString)
			{
				case "AlgorithmBox.FlowCharts.Start": return new Start();
				case "AlgorithmBox.FlowCharts.Stop": return new Stop();
				case "AlgorithmBox.FlowCharts.Process": return new Process();
				case "AlgorithmBox.FlowCharts.Decision": return new Decision();
				case "AlgorithmBox.FlowCharts.Input": return new Input();
				default: return null;
			}
		}

		void FillDataToElement(FlowChartElement fl, string[] data)
		{
			fl.ID = UInt32.Parse(data[1]);
			fl.Width = Double.Parse(data[4]);
			fl.Height = Double.Parse(data[5]);
			fl.SetValue(Canvas.LeftProperty, Double.Parse(data[2]) + fl.Width / 2);
			fl.SetValue(Canvas.TopProperty, Double.Parse(data[3]));
		}

		void ClearAllElements()
		{
			List<FlowChartElement> elements = new List<FlowChartElement>();
			foreach (FlowChartElement fl in this.Sketch.Children.OfType<FlowChartElement>())
				elements.Add(fl);
			foreach (FlowChartElement fl in elements)
				this.Sketch.Children.Remove(fl);

			List<LineGeometry> lines = new List<LineGeometry>();
			foreach (LineGeometry line in this.connectionLinesGeometry.Children.OfType<LineGeometry>())
				lines.Add(line);
			foreach (LineGeometry line in lines)
				this.connectionLinesGeometry.Children.Remove(line);
		}

		void ConnectElements(uint from, uint to, Point sp, Point ep)
		{
			FlowChartElement fromFl = null;
			FlowChartElement toFl = null;

			foreach (FlowChartElement fl in this.Sketch.Children.OfType<FlowChartElement>())
			{
				if (fl.ID == from)
					fromFl = fl;
				if (fl.ID == to)
					toFl = fl;
			}

			if (fromFl != null && toFl != null)
			{
				fromFl.Link = new ConnectionLine();
				fromFl.Link.Next = toFl;
				ConnectionLine prev = new ConnectionLine();
				prev.Next = fromFl;
				toFl.PrevLink.Add(prev);

				LineGeometry line = new LineGeometry();
				line.StartPoint = sp;
				line.EndPoint = ep;
				prev.Line = line;
				fromFl.Link.Line = line;
				this.connectionLinesGeometry.Children.Add(line);
			}
		}
	}
}
